#include "PageProcessor.h"
#include "TextUtils.h"
#include <cppjieba/Jieba.hpp>
#include <string>
#include <set>
#include <fstream>
#include <sstream>
#include <cmath>
#include <iomanip>
#include "LogUtils.h"

using std::cout;
using std::cerr;
using std::endl;
using std::string;
using std::set;
using std::ifstream;
using std::ofstream;
using std::ostringstream;

PageProcessor::PageProcessor() : m_tokenizer(), m_hasher(){
    // 构造函数
    // 读取停用词
    LOG_INFO << "[PageProcessor] Generating m_stopWords...";
    generate_stopwords("corpus/stopwords/cn_stopwords.txt", m_stopWords);
    generate_stopwords("corpus/stopwords/en_stopwords.txt", m_stopWords);
}

void PageProcessor::process(const string& dir){
    // 提取数据到m_documents, 提取的时候要清洗
    extract_documents(dir);

    // 数据去重, 针对m_documents集合元素
    deduplicate_documents();

    // 生成网页库和网页偏移库文件
    build_pages_and_offsets("corpus/page_library.dat", "corpus/page_offset.dat");

    // 生成倒排索引库
    build_inverted_index("corpus/inverted_index.dat");

    LOG_INFO << "[PageProcessor] Process finished...";
}

void PageProcessor::extract_documents(const string& dir){
    LOG_INFO << "[PageProcessor] Extract_documents...";

    // 先把文章的信息写入到m_documents, 最后再写入page_library.dat
    vector<string> files = DirectoryScanner::scan(dir);

    for(auto& file : files){
        // 将每个语料文件的信息读入m_documents;
        add_document_by_xml(dir + "/" + file, m_documents);
    }

    // 再对content进行清洗
    for(auto& doc : m_documents){
        clean_content_text(doc.content);
    }
    
}

void PageProcessor::deduplicate_documents(){
    LOG_INFO << "[PageProcessor] Deduplicate_documents...";
    // 数据去重, 针对m_documents的元素
    size_t topN = 100;

    // 存储需要删除的文档索引（从后往前删更高效）
    set<int> indices_to_remove;
    
    // 存储所有已处理文档的 SimHash 值
    vector<uint64_t> processed_simhashes;
    int id_count = 0;
    
    size_t last_percent_displayed = 0;

    size_t all_size = m_documents.size();
    if (all_size == 0){
        // 处理空列表情况
        cout << "文档列表为空，无需去重。" << endl;
        return;
    }

    // 1. 遍历文档并进行去重比较
    for (size_t i = 0; i < m_documents.size(); ++i){
        auto &doc = m_documents[i]; // 使用索引访问，避免引用失效问题
        uint64_t current_hash;

        // a. 计算 SimHash
        m_hasher.make(doc.content, topN, current_hash);
        bool is_duplicate = false;

        // b. 与所有已处理的 SimHash 进行比较
        for (auto &sim_hash : processed_simhashes){
            if (Simhasher::isEqual(sim_hash, current_hash, 3)){
                // cout << "id:" << i << "发生冲突" << endl;
                // 相似，标记为重复，记录索引
                indices_to_remove.insert(i);
                is_duplicate = true;
                break; // 找到一个相似的即可
            }
        }

        // c. 如果不是重复的，将新的 SimHash 加入集合，供后续文档比较
        if (!is_duplicate){
            processed_simhashes.push_back(current_hash);
            doc.id = ++id_count;
        }

        // 当前已处理数量
        size_t current_processed = i + 1;

        // 1. 计算当前的整数百分比（0 - 100）
        size_t current_percent = (static_cast<unsigned long long>(current_processed) * 100) / all_size;

        // 2. 判断条件：只有当进度增加时才显示
        if (current_percent > last_percent_displayed){

            // 3. 更新状态变量
            last_percent_displayed = current_percent;

            // 4. 显示进度条 (使用 printf/fflush 比 cout 更高效)
            printf("去重进度: %zu%% 完成 (%zu/%zu) ...\r",
                   current_percent, current_processed, all_size);
            fflush(stdout);
        }
    }

    // 【收尾工作：确保 100% 打印和换行】
    if (last_percent_displayed != 100){
        // 重新打印 100%，并使用 \n 换行
        printf("去重进度: 100%% 完成 (%zu/%zu)...\n", all_size, all_size);
    }
    else{
        // 如果最后一次打印是 100% (\r)，则单独换行
        printf("\n");
    }

    // 2. 集中执行删除操作 (从后往前删除，避免索引错位)
    // 使用反向迭代器或倒序循环
    for (auto it = indices_to_remove.rbegin(); it != indices_to_remove.rend(); ++it){
        // 使用 std::vector::erase 成员函数
        m_documents.erase(m_documents.begin() + *it);
    }
}

std::streamoff get_single_doc_size(int id, const char* link, const char* title, const char* content) {
    XMLDocument temp_doc; 
    
    // 1. 创建 <doc> 节点，并使其成为临时文档的根节点
    XMLElement* docNode = temp_doc.NewElement("doc");
    temp_doc.InsertFirstChild(docNode); 
    
    // 2. 仿照你的 create_xml_doc 逻辑，填充子节点
    //    注意：这里不能使用 docNode->InsertNewChildElement(...)，因为 docNode 
    //    现在是根节点，我们必须使用 temp_doc.NewElement 来创建子节点。

    XMLElement* idNode = temp_doc.NewElement("id");
    idNode->SetText(id); 
    docNode->InsertEndChild(idNode);

    XMLElement* linkNode = temp_doc.NewElement("link");
    linkNode->SetText(link);
    docNode->InsertEndChild(linkNode);

    XMLElement* titleNode = temp_doc.NewElement("title");
    titleNode->SetText(title);
    docNode->InsertEndChild(titleNode);

    XMLElement* contentNode = temp_doc.NewElement("content");
    contentNode->SetText(content);
    docNode->InsertEndChild(contentNode);
    
    // 序列化到内存打印机
    XMLPrinter printer;

    // TinyXML-2 默认是紧凑输出
    temp_doc.Print(&printer); 
    
    const char* serialized_str = printer.CStr();
    if (!serialized_str) {
        return 0; 
    }

    std::streamoff size = static_cast<std::streamoff>(std::strlen(serialized_str));
    
    return size; 
}

void PageProcessor::build_pages_and_offsets(const string& pages, const string& offsets){
    LOG_INFO << "[PageProcessor] Generating page_library.dat...";
    // 写入page_library.dat
    XMLDocument doc;
    
    // 写入网页偏移库文件
    ofstream file{ offsets };
    std::streamoff curr_offset = 11;

    // 必须先创建根节点，例如 <docs>
    doc.InsertFirstChild(doc.NewElement("docs"));
    
    // 将m_documents的内容写入到docs节点下
    for (auto &item : m_documents){
        if(item.content.size() == 0){
            // 跳过那些数据清洗之后content为空的item
            continue;
        }

        create_xml_doc(doc, item.id, item.link.c_str(), item.title.c_str(), item.content.c_str());
        std::streamoff content_size = get_single_doc_size(item.id, item.title.c_str(), item.content.c_str(), item.link.c_str());
        content_size += static_cast<std::streamoff>(24); // magic_number
        
        // 写入page_offset.dat
        file << to_string(item.id)  << " " << to_string(curr_offset) << " ";
        curr_offset += content_size;
        file << to_string(content_size) << endl;
    }

    LOG_INFO << "[PageProcessor] Generating page_offset.dat...";
    std::ofstream output_xml_file{ pages };
    XMLPrinter final_printer;
    // 关键：不调用 SetIndent，依赖 TinyXML 的默认紧凑行为！
    doc.Print(&final_printer);

    output_xml_file << final_printer.CStr();
    output_xml_file.close();
}

//生成倒排索引
void PageProcessor::build_inverted_index(const string& filename){
    LOG_INFO << "[PageProcessor] Generating inverted_index.dat...";

    int N = m_documents.size();
    map<string,int> DF;

    // 缓存每个文档的关键字统计结果
    vector<map<string,int>> allKeyWordCounts(N);

    // 第一次遍历：分词 + 统计关键字 + 更新DF
    for(int i=0; i<N; ++i){
        auto &doc = m_documents[i];
        vector<string> words;
        m_tokenizer.Cut(doc.title + doc.content, words);

        map<string,int> keyWordCount;
        for(auto &word: words){
            if(isValidKeyWord(word, m_stopWords)){
                ++keyWordCount[word];
            }
        }
        // 保存结果
        allKeyWordCounts[i] = std::move(keyWordCount);

        // 更新DF
        for(auto &[word, amount] : allKeyWordCounts[i]){
            ++DF[word];
        }
    }

    // 第二次遍历：计算TF-IDF并归一化
    for(int i=0; i<N; ++i){
        auto &doc = m_documents[i];
        auto &keyWordCount = allKeyWordCounts[i];

        double sum_of_squres_weight = 0;
        int totalKeyWords = 0;
        for(auto &[word, amount] : keyWordCount){
            totalKeyWords += amount;
        }

        for(auto &[word, amount] : keyWordCount){
            double TF = static_cast<double>(amount)/static_cast<double>(totalKeyWords);
            double IDF = log(static_cast<double>(N)/static_cast<double>(DF[word]));
            double weight = TF * IDF;
            m_invertedIndex[word][doc.id] = weight;
            sum_of_squres_weight += weight*weight;
        }

        for(auto &[word, amount] : keyWordCount){
            auto &set_info = m_invertedIndex[word];
            set_info[doc.id] /= sqrt(sum_of_squres_weight);
        }
    }

    // 输出结果
    ostringstream oss;
    for(auto &[keyword, info] : m_invertedIndex){
        oss << keyword << " ";
        for(auto it = info.begin(); it != info.end(); ++it){
            oss << it->first << " " << std::fixed << std::setprecision(7) << it->second;
            if(std::next(it) != info.end()){
                oss << " ";
            }
        }
        oss << endl;
    }
    ofstream ofs{filename};
    ofs << oss.str();
    ofs.close();
}

// 在 UTF-8 编码中，汉字和中文标点都是多字节字符，其起始字节值大于 0x7F。
bool is_chinese_or_multibyte(unsigned char c) {
    // 只要大于 127，我们就认为是需要保留的非ASCII字符，用于判断数字的上下文。
    return c > 0x7F;
}

// 清洗每个item中content的内容
void PageProcessor::clean_content_text(string& content){
    string final_cleaned_content;
    size_t n = content.length();

    for (size_t i = 0; i < n;)
    {
        unsigned char current_char = static_cast<unsigned char>(content[i]);

        // 1. **去除英文字母和英文标点** (沿用上次的简单过滤)
        // 注意：这里仍然假设英文标点是单字节的。
        if (std::isalpha(current_char) || std::ispunct(current_char))
        {
            i++;
            continue;
        }

        // 2. **核心逻辑：数字序列处理**
        if (std::isdigit(current_char))
        {
            // 找到数字序列的开始
            size_t start_digit = i;
            size_t end_digit = i;

            // 找到整个连续数字序列的结束位置
            while (end_digit < n && std::isdigit(static_cast<unsigned char>(content[end_digit])))
            {
                end_digit++;
            }

            // --- 检查数字序列的前后上下文 ---
            bool has_valid_neighbor = false;

            // 检查前面：是否紧邻一个汉字或中文标点
            if (start_digit > 0)
            {
                // 检查前一个字符是否为多字节字符 (即汉字或中文标点)
                if (is_chinese_or_multibyte(static_cast<unsigned char>(content[start_digit - 1])))
                {
                    has_valid_neighbor = true;
                }
            }

            // 检查后面：是否紧邻一个汉字或中文标点
            if (!has_valid_neighbor && end_digit < n)
            {
                // 检查后一个字符是否为多字节字符 (即汉字或中文标点)
                if (is_chinese_or_multibyte(static_cast<unsigned char>(content[end_digit])))
                {
                    has_valid_neighbor = true;
                }
            }

            // --- 结论：保留或移除 ---
            if (has_valid_neighbor)
            {
                // 保留整个数字序列
                final_cleaned_content.append(content, start_digit, end_digit - start_digit);
            }
            // 如果没有有效邻居，则不添加到 final_cleaned_content，实现了移除。

            // 移动 i 到数字序列的末尾，继续下一个字符的检查
            i = end_digit;
        }

        // 3. **保留其他字符（中文、空格、换行、中文标点等）**
        else
        {
            final_cleaned_content += content[i];
            i++;
        }
    }

    // 赋值回引用参数 content
    content = final_cleaned_content;
    
}